﻿using Google.ProtocolBuffers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Linq;
using System.Diagnostics;
using System.Threading;

namespace LightingPlatform.Communication
{
    public partial class CommunicatorClient
    {
        private static readonly CommunicatorClient _current = new CommunicatorClient();
        private const byte Head = 0xAA;
        private const byte Tail = 0xFF;
        private static int _sendNumber;
        private MemoryStream _stream;
        private Dictionary<int, TaskCompletionSource<byte[]>> _callDictionary;

        public static EndPoint RemoteEndPoint
        {
            get;
            set;
        }

        private TcpChannel TcpChannel
        {
            get;
            set;
        }

        private bool IsConnected
        {
            get
            {
                return this.TcpChannel == null ? false : this.TcpChannel.State == TcpChannelState.Connected;
            }
        }

        public static CommunicatorClient Current
        {
            get
            {
                return _current;
            }
        }

        private CommunicatorClient()
        {
            _callDictionary = new Dictionary<int, TaskCompletionSource<byte[]>>();
        }

        public async Task<bool> ConnectAsync()
        {
            if (this.TcpChannel == null || this.TcpChannel.State != TcpChannelState.Disconnected)
            {
                this.TcpChannel = new TcpChannel();
                this.TcpChannel.Received += this.TcpChannelReceived;
                this.TcpChannel.Error += this.TcpChannelError;
            }

            return await this.TcpChannel.ConnectAsync(RemoteEndPoint);
        }

        private void TcpChannelReceived(object sender, TcpChannelReceiveEventArgs e)
        {
            var data = e.Data;

            for (int i = 0; i < data.Length; i++)
            {
                byte value = data[i];

                switch (value)
                {
                    case Head:
                        if (i == 0)
                        {
                            _stream = new MemoryStream();
                        }
                        else
                        {
                            if (this._stream != null)
                            {
                                this._stream.WriteByte(data[i]);
                            }
                        }
                        break;
                    case Tail:
                        if (i == data.Length - 1)
                        {
                            _stream.Position = 0;
                            //Debug.WriteLine(Utility.ByteArrayToHexString(this.Stream.ToArray()));
                            Unpack(this._stream);
                            _stream = null;
                        }
                        break;
                    default:
                        if (this._stream != null)
                        {
                            this._stream.WriteByte(data[i]);
                        }
                        break;
                }
            }
        }

        public async Task<byte[]> Call(CommandType command, byte[] data = null)
        {
            var number = Pack((ushort)command, data);
            var tcs = new TaskCompletionSource<byte[]>();
            _callDictionary[number] = tcs;

            return await tcs.Task;
        }

        #region Pack
        private int Pack(ushort command, byte[] data)
        {
            _sendNumber++;
            this.Pack(command, _sendNumber, 0, data);
            return _sendNumber;
        }

        private void Pack(ushort commandId, int sendNumber, int receiveNumber, byte[] data)
        {
            byte[] header = new byte[10];
            header[0] = (byte)(commandId >> 8);
            header[1] = (byte)(commandId >> 0);
            header[2] = (byte)(sendNumber >> 24);
            header[3] = (byte)(sendNumber >> 16);
            header[4] = (byte)(sendNumber >> 8);
            header[5] = (byte)(sendNumber >> 0);
            header[6] = (byte)(receiveNumber >> 24);
            header[7] = (byte)(receiveNumber >> 16);
            header[8] = (byte)(receiveNumber >> 8);

            header[9] = (byte)(receiveNumber >> 0);

            MemoryStream stream = new MemoryStream();
            stream.WriteByte(Head);
            stream.Write(header, 0, header.Length);

            if (data != null && data.Length > 0)
            {
                stream.Write(data, 0, data.Length);
            }

            stream.WriteByte(Tail);
            this.TcpChannel.Send(stream.ToArray());
        }
        #endregion

        #region Unpack
        private int Unpack(MemoryStream stream)
        {
            ushort commandId;
            int sendNumber;
            int receiveNumber;
            byte[] data;

            Unpack(stream, out commandId, out sendNumber, out receiveNumber, out data);
            Debug.WriteLine("Unpack:CommandId:{0},ReceiveNumber:{1}", commandId, receiveNumber);

            TaskCompletionSource<byte[]> tcs;
            if (_callDictionary.TryGetValue(receiveNumber, out tcs))
            {
                _callDictionary.Remove(receiveNumber);
                tcs.SetResult(data);
            }
            return sendNumber;
        }

        private void Unpack(MemoryStream stream, out ushort commandId, out int sendNumber, out int receiveNumber, out byte[] data)
        {
            byte header0 = (byte)stream.ReadByte();
            byte header1 = (byte)stream.ReadByte();
            commandId = (ushort)((header0 << 8) + header1);

            byte header2 = (byte)stream.ReadByte();
            byte header3 = (byte)stream.ReadByte();
            byte header4 = (byte)stream.ReadByte();
            byte header5 = (byte)stream.ReadByte();
            sendNumber = (header2 << 24) + (header3 << 16) + (header4 << 8) + (header5 << 0);

            byte header6 = (byte)stream.ReadByte();
            byte header7 = (byte)stream.ReadByte();
            byte header8 = (byte)stream.ReadByte();
            byte header9 = (byte)stream.ReadByte();
            receiveNumber = (header6 << 24) + (header7 << 16) + (header8 << 8) + header9;

            List<byte> list = new List<byte>();
            while (stream.Position < stream.Length)
            {
                list.Add((byte)stream.ReadByte());
            }

            data = list.ToArray();
        }
        #endregion

        public void Reset()
        {
            if (this.TcpChannel != null)
            {
                this.TcpChannel.Received -= this.TcpChannelReceived;
                this.TcpChannel.Error -= this.TcpChannelError;
                this.TcpChannel.Close();
            }
        }

        private void TcpChannelError(object sender, TcpChannelErrorEventArgs e)
        {
            Debug.WriteLine(e.Exception.Message);

            Deployment.Current.Dispatcher.BeginInvoke(async () =>
            {
                this.OnStateChanged(1);

                while (true)
                {
                    if (this.TcpChannel != null)
                    {
                        this.TcpChannel.Received -= this.TcpChannelReceived;
                        this.TcpChannel.Error -= this.TcpChannelError;
                        this.TcpChannel.Close();
                        this.TcpChannel = null;
                    }


                    await Task.Factory.StartNew(() => Thread.Sleep(1000));

                    try
                    {
                        await this.ConnectAsync();
                    }
                    catch
                    {
                    }
                }
            });
        }

        #region StateChanged
        public event EventHandler<CommunicatorClientStateChanged> StateChanged;

        protected void OnStateChanged(int state)
        {
            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                if (this.StateChanged != null)
                {
                    this.StateChanged(this, new CommunicatorClientStateChanged(state));
                }
            });
        }
        #endregion
    }

    public class CommunicatorClientStateChanged : EventArgs
    {
        public CommunicatorClientStateChanged(int state)
        {
            this.State = state;
        }

        public int State
        {
            get;
            private set;
        }
    }
}
